package bg.tu.varna.pms.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Properties;
import javax.xml.bind.DatatypeConverter;
import org.omg.CORBA.portable.ApplicationException;
import bg.tu.varna.pms.model.ChatRoom;
import bg.tu.varna.pms.model.Contact;

public class UserCache {
	private static final File mDataFolder;
	private static final File mApplicationPropertiesFile;
	private static final Properties mApplicationProperties;
	private static final Properties mUserDefaultProperties,
			mApplicationDefaultProperties;

	private final String mUsername;
	private final File mCacheFolder;
	private final File mCacheProperties;
	private final File mCacheContacts;
	private final File mCacheRooms;

	private Properties mProperties;

	static {
		// Windows environment variable
		String vDataFolder = System.getenv( "APPDATA" );
		if ( vDataFolder == null ) {
			// Other OS user home directory ??
			vDataFolder = System.getProperty( "user.home" );
		}
		if ( !vDataFolder.endsWith( File.separator ) ) {
			vDataFolder += ( File.separator + "MYPE" + File.separator );
		} else {
			vDataFolder += ( "MYPE" + File.separator );
		}

		mDataFolder = new File( vDataFolder );
		mApplicationPropertiesFile = new File( mDataFolder, "application.properties" );
		mApplicationProperties = new Properties();

		mApplicationDefaultProperties = new Properties();
		mApplicationDefaultProperties.setProperty( "mype.host", "127.0.0.1" );
		mApplicationDefaultProperties.setProperty( "mype.port", DatatypeConverter.printInt( 7777 ) );

		mUserDefaultProperties = new Properties();
		mUserDefaultProperties.setProperty( "mype.sync.time", DatatypeConverter.printDateTime( null ) );
		mUserDefaultProperties.setProperty( "mype.sync.contacts.time", DatatypeConverter.printDateTime( null ) );
		mUserDefaultProperties.setProperty( "mype.sync.rooms.time", DatatypeConverter.printDateTime( null ) );
		mUserDefaultProperties.setProperty( "mype.sync.files.time", DatatypeConverter.printDateTime( null ) );

		if ( !mDataFolder.exists() ) {
			try {
				generateGeneralCache();
			} catch ( IOException e ) {
				e.printStackTrace( System.err );
				System.exit( -1 );
			}
		}

		try {
			mApplicationProperties.load( new FileInputStream( mApplicationPropertiesFile ) );
		} catch ( FileNotFoundException e ) {
			e.printStackTrace( System.err );
			System.exit( -1 );
		} catch ( IOException e ) {
			e.printStackTrace( System.err );
			System.exit( -1 );
		}
	}

	UserCache( String username ) throws ApplicationException {
		mUsername = username;
		mCacheFolder = getUserCacheFolder( username );
		mCacheProperties = new File( mCacheFolder + "cache.properties" );
		mCacheContacts = new File( mCacheFolder + "contacts.dat" );
		mCacheRooms = new File( mCacheFolder + "rooms.dat" );

		if ( !mCacheFolder.exists() ) {
			try {
				generateCache();
			} catch ( IOException e ) {
				throw new ApplicationException( "Cannot generate cache for user" + username, null );
			}
		}

		mProperties = new Properties();
		try {
			mProperties.loadFromXML( new FileInputStream( mCacheProperties ) );
		} catch ( IOException e ) {
			throw new ApplicationException( "Cannot load properties for user" + username, null );
		}
	}

	static File getUserCacheFolder( String username ) {
		return new File( mDataFolder + username.toLowerCase() + File.separator );
	}

	public static boolean isUserCached( String username ) {
		return getUserCacheFolder( username ).exists();
	}

	private static void generateGeneralCache() throws IOException {
		mDataFolder.mkdirs();
		mApplicationPropertiesFile.createNewFile();
		mApplicationDefaultProperties.store( new FileOutputStream( mApplicationPropertiesFile ), "Initial properties" );
	}

	private void generateCache() throws IOException {
		mCacheFolder.mkdirs();
		mCacheProperties.createNewFile();
		mCacheContacts.createNewFile();
		mCacheRooms.createNewFile();
		mUserDefaultProperties.storeToXML( new FileOutputStream( mCacheProperties ), "Initial properties" );
	}

	public static String getAppProp_Host() {
		return mApplicationProperties.getProperty( "mype.host" );
	}
	public static int getAppProp_Port() {
		return DatatypeConverter.parseInt( mApplicationProperties.getProperty( "mype.port" ) );
	}

	public void setContactList( Contact ... contacts ) throws ApplicationException {
		this.<Contact> setToCache( "mype.sync.contacts.time", mCacheContacts, "contact list", contacts );
	}

	public Contact[] getContactList() throws ApplicationException {
		return this.<Contact> getFromCache( mCacheContacts, "contact list" );
	}

	public Calendar getContactListSync() {
		return getSync( "mype.sync.contacts.time" );
	}

	public void setChatRoom( ChatRoom ... chatRooms ) throws ApplicationException {
		this.<ChatRoom> setToCache( "mype.sync.rooms.time", mCacheRooms, "chat rooms", chatRooms );
	}

	public ChatRoom[] getChatRooms() throws ApplicationException {
		return this.<ChatRoom> getFromCache( mCacheRooms, "chat rooms" );
	}

	public Calendar getChatRoomsSync() {
		return getSync( "mype.sync.rooms.time" );
	}

	private <T extends Serializable> void setToCache( String syncPropertyKey, File cacheFile, String entity, T[] obj ) throws ApplicationException {
		try ( FileOutputStream vOut = new FileOutputStream( cacheFile );
				ObjectOutputStream vObjOut = new ObjectOutputStream( vOut ) ) {
			vObjOut.writeObject( obj );
			vObjOut.flush();
			vObjOut.close();
			mProperties.setProperty( syncPropertyKey, DatatypeConverter.printDateTime( GregorianCalendar.getInstance() ) );
			mProperties.storeToXML( new FileOutputStream( mCacheProperties ), "Sync of " + entity );
		} catch ( FileNotFoundException e ) {
			e.printStackTrace( System.err );
			throw new ApplicationException( "Cannot save " + entity + ". The file is missing or inaccessible.", null );
		} catch ( IOException e ) {
			e.printStackTrace( System.err );
			throw new ApplicationException( "Cannot save " + entity + ". The file is missing or inaccessible.", null );
		}
	}

	@SuppressWarnings( "unchecked" )
	private <T extends Serializable> T[] getFromCache( File cacheFile, String entity ) throws ApplicationException {
		try ( FileInputStream vIn = new FileInputStream( cacheFile );
				ObjectInputStream vObjIn = new ObjectInputStream( vIn ) ) {
			return ( (T[]) ( vObjIn.readObject() ) );
		} catch ( ClassNotFoundException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace( System.err );
			throw new ApplicationException( "Cannot load " + entity + ". The class is not loaded for some reason.", null );
		} catch ( IOException e ) {
			e.printStackTrace( System.err );
			throw new ApplicationException( "Cannot load " + entity + ". The file is missing or inaccessible.", null );
		}
	}

	private Calendar getSync( String propertyKey ) {
		return DatatypeConverter.parseDateTime( mProperties.getProperty( propertyKey ) );
	}

}
